package org.sgx.gwteditors.client.impl1.misc;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.sgx.gwteditors.client.editor.Editor;
import org.sgx.gwteditors.client.editor.error.EditorError;
import org.sgx.gwteditors.client.editor.error.EditorErrorManager;
import org.sgx.gwteditors.client.editor.event.EditorEventManager;
import org.sgx.gwteditors.client.editor.event.ValueChangeListener;
import org.sgx.gwteditors.client.impl1.EditorFramework1;
import org.sgx.gwteditors.client.impl1.EditorManager1;
import org.sgx.gwteditors.client.impl1.basetypes.HTML5File;
import org.sgx.gwteditors.client.impl1.complex.PropertyHaverEditor2;
import org.sgx.gwteditors.client.impl1.misc.HTML5FileEditor.FileReadListener;
import org.vectomatic.dnd.DataTransferExt;
import org.vectomatic.dnd.DropPanel;
import org.vectomatic.file.ErrorCode;
import org.vectomatic.file.File;
import org.vectomatic.file.FileError;
import org.vectomatic.file.FileList;
import org.vectomatic.file.FileReader;
import org.vectomatic.file.events.ErrorEvent;
import org.vectomatic.file.events.ErrorHandler;
import org.vectomatic.file.events.LoadEndEvent;
import org.vectomatic.file.events.LoadEndHandler;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DragEnterEvent;
import com.google.gwt.event.dom.client.DragEnterHandler;
import com.google.gwt.event.dom.client.DragLeaveEvent;
import com.google.gwt.event.dom.client.DragLeaveHandler;
import com.google.gwt.event.dom.client.DragOverEvent;
import com.google.gwt.event.dom.client.DragOverHandler;
import com.google.gwt.event.dom.client.DropEvent;
import com.google.gwt.event.dom.client.DropHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class HTML5FileEditor extends Composite implements Editor<List<HTML5File>> {

	private static DriveFiles1UiBinder uiBinder = GWT.create(DriveFiles1UiBinder.class);

	interface DriveFiles1UiBinder extends UiBinder<Widget, HTML5FileEditor> {
	}

	private FileReader reader;
	private List<File> readQueue;
	Map<File, Editor<HTML5File>> editors;

	@UiField
	VerticalPanel dropFilesContainer;
	FlowPanel fileListPanel;
	private FileReadListener readFileCallback;

	public HTML5FileEditor() {
		initWidget(uiBinder.createAndBindUi(this));
		editors = new HashMap<File, Editor<HTML5File>>();
		initReader();
		initDropFiles();
	}

	@Override
	public boolean isSimple() {
		return false;
	}

	@Override
	public void load(List<HTML5File> t) {
		// TODO: in general won't load anything, only flush
	}

	@Override
	public List<HTML5File> flush() {
		List<HTML5File> l = new LinkedList<HTML5File>();
		for (Editor<HTML5File> ed : editors.values()) {
			HTML5File f = ed.flush();
			l.add(f);
		}
		return l;
		// no validation / errors
	}

	@Override
	public Widget getWidget() {
		return this;
	}

	@Override
	public void addValueChangeListener(ValueChangeListener<List<HTML5File>> l) {
		EditorEventManager.<List<HTML5File>> getInstance().addValueChangeListener(this, l);
	}

	@Override
	public void removeValueChangeListener(ValueChangeListener<List<HTML5File>> l) {
		EditorEventManager.<List<HTML5File>> getInstance().removeValueChangeListener(this, l);
	}

	@Override
	public List<EditorError<List<HTML5File>>> getErrors() {
		return EditorErrorManager.<List<HTML5File>> getInstance().getErrors(this);
	}

	@Override
	public String getEditorId() {
		return EditorManager1.getInstance().getEditorName(this);
	}

	@Override
	public String[] getEditorTypesSupported() {
		return new String[] { EditorFramework1.TYPE_STRING };
	}

	private void initReader() {
		reader = new FileReader();
		reader.addLoadEndHandler(new LoadEndHandler() {
			/**
			 * This handler is invoked when FileReader.readAsText(), FileReader.readAsBinaryString() or FileReader.readAsArrayBuffer() successfully completes
			 */
			@Override
			public void onLoadEnd(LoadEndEvent event) {
				if (reader.getError() == null) {
					if (readQueue.size() > 0) {
						File file = readQueue.get(0);
						String content = reader.getStringResult();
						Editor<HTML5File> fileEditor = editors.get(file);
						HTML5File fileDrive = fileEditor.flush();
						fileDrive.setContent(content);
						try {
						} finally {
							readQueue.remove(0);
							readNextFile();
						}
					}
				}
			}
		});
		reader.addErrorHandler(new ErrorHandler() {
			/**
			 * This handler is invoked when FileReader.readAsText(), FileReader.readAsBinaryString() or FileReader.readAsArrayBuffer() fails
			 */
			@Override
			public void onError(ErrorEvent event) {
				if (readQueue.size() > 0) {
					File file = readQueue.get(0);
					handleError(file);
					readQueue.remove(0);
					readNextFile();
				}
			}
		});

		readQueue = new LinkedList<File>();
	}

	void handleError(File file) {
		FileError error = reader.getError();
		String errorDesc = "";
		if (error != null) {
			ErrorCode errorCode = error.getCode();
			if (errorCode != null) {
				errorDesc = ": " + errorCode.name();
			}
		}
		Window.alert("File loading error for file: " + file.getName() + "\n" + errorDesc);
	}

	private void readNextFile() {
		if (readQueue.size() > 0) {
			File file = readQueue.get(0);
			reader.readAsText(file);
			try {

				reader.readAsText(file);
			} catch (Throwable t) {
				// Necessary for FF (see bug https://bugzilla.mozilla.org/show_bug.cgi?id=701154)
				// Standard-complying browsers will not go in this branch
				handleError(file);
				readQueue.remove(0);
				readNextFile();
			}
		} else
			afterReadAllFiles();
	}

	public static interface FileReadListener {
		void ready(List<HTML5File> l);
	}

	public void flush(final FileReadListener callback) {
		this.readFileCallback = callback;
		readNextFile();
	}

	private void afterReadAllFiles() {
		List<HTML5File> files = flush();
		readFileCallback.ready(files);
	}

	protected void proccessDropFiles(DropEvent event) {
		DataTransferExt dte = event.getDataTransfer().<DataTransferExt> cast();
		FileList files = dte.getFiles();
		for (int i = 0; i < files.getLength(); i++) {
			File file = files.getItem(i);
			readQueue.add(file);
			addFileToList(file);
		}
	}

	private void addFileToList(File file) {
		HTML5File df = HTML5File.from(file);
		PropertyHaverEditor2<HTML5File> ed = new PropertyHaverEditor2<HTML5File>();
		fileListPanel.add(new HTML("<h3 style=\"padding-top: 4px\">"+file.getName()+"</h3>")); 
		fileListPanel.add(ed);
		ed.load(df);
		editors.put(file, ed);
	}

	private void initDropFiles() {
		DropPanel dp = new DropPanel();
		dp.add(new Label("drop your files here"));
		dropFilesContainer.add(dp);

		dp.addDragEnterHandler(new DragEnterHandler() {
			@Override
			public void onDragEnter(DragEnterEvent event) {
				event.stopPropagation();
				event.preventDefault();
			}
		});

		dp.addDragLeaveHandler(new DragLeaveHandler() {
			@Override
			public void onDragLeave(DragLeaveEvent event) {
				event.stopPropagation();
				event.preventDefault();
			}
		});

		dp.addDragOverHandler(new DragOverHandler() {
			@Override
			public void onDragOver(DragOverEvent event) {

				event.stopPropagation();
				event.preventDefault();
			}
		});

		dp.addDropHandler(new DropHandler() {
			@Override
			public void onDrop(DropEvent event) {
				proccessDropFiles(event);
				event.stopPropagation();
				event.preventDefault();
			}
		});

		fileListPanel = new FlowPanel();
		dropFilesContainer.add(fileListPanel);

	}

}
